#pragma once
#include <Rz/ReferenceCounter.h>
#include <Rz/Diagnostics/PtrManager.h>

namespace Rz {

template <typename T>
class Ptr
{
	public:
		typedef T ValueType;

		RZ_INLINE Ptr() :
			_ptr(nullptr)
		{
			
		}

		RZ_INLINE Ptr(T* ptr) :
			_ptr(ptr)
		{
			if (_ptr)
			{
				RZ_PTRMANAGER_ADDREF(_ptr);
				_ptr->Ref();
			}
		}

		RZ_INLINE Ptr(const Ptr& rp) :
			_ptr(rp._ptr)
		{
			if (_ptr)
			{
				RZ_PTRMANAGER_ADDREF(_ptr);
				_ptr->Ref();
			}
		}

		template <class T2>
		RZ_INLINE Ptr(const Ptr<T2>& rp) :
			_ptr(rp._ptr)
		{
			if (_ptr)
			{
				RZ_PTRMANAGER_ADDREF(_ptr);
				_ptr->Ref();
			}
		}

		RZ_INLINE ~Ptr()
		{
			if (_ptr)
			{
				RZ_PTRMANAGER_REMREF(_ptr);
				_ptr->Unref();
			}
		}

		Ptr& operator = (const Ptr& rp)
		{
			Assign(rp);
			return *this;
		}

		template <class T2> 
		Ptr& operator = (const Ptr<T2> &rp)
		{
			Assign(rp);
			return *this;
		}

		RZ_INLINE Ptr& operator = (T* ptr)
		{
			if (_ptr == ptr)
			{
				return *this;
			}

			T* tmp_ptr = _ptr;
			_ptr = ptr;

			if (_ptr)
			{
				RZ_PTRMANAGER_ADDREF(_ptr);
				_ptr->Ref();
			}

			if (tmp_ptr)
			{
				RZ_PTRMANAGER_REMREF(tmp_ptr);
				tmp_ptr->Unref();
			}

			return *this;
		}

		RZ_INLINE operator T* () const
		{
			return _ptr;
		}

		RZ_INLINE T& operator *() const
		{
			return *_ptr;
		}

		RZ_INLINE T* operator ->() const
		{
			return _ptr;
		}

		RZ_INLINE T* Get() const
		{
			return _ptr;
		}

		RZ_INLINE bool operator !() const
		{
			return _ptr == nullptr;
		}

		RZ_INLINE bool IsValid() const
		{ 
			return _ptr != nullptr;
		}

	private:
		template <class T2>
		void Assign(const Ptr<T2>& rp)
		{
			if (_ptr == rp._ptr)
			{
				return;
			}

			T* tmp_ptr = _ptr;
			_ptr = rp._ptr;

			if (_ptr)
			{
				RZ_PTRMANAGER_ADDREF(_ptr);
				_ptr->Ref();
			}

			if (tmp_ptr)
			{
				RZ_PTRMANAGER_REMREF(tmp_ptr);
				tmp_ptr->Unref();
			}
		}

		template <class T2> friend class Ptr;

		T* _ptr;
};

} 
